package com.yuantu.judiciary.serviceimpl;

import com.yuantu.judiciary.constant.Regulation;
import com.yuantu.judiciary.dao.IAccountDao;
import com.yuantu.judiciary.dao.IOrganizationDao;
import com.yuantu.judiciary.dao.ISysOperLogApiDao;
import com.yuantu.judiciary.dao.IVipOrderDao;
import com.yuantu.judiciary.exception.enums.OrganizationExceptionEnum;
import com.yuantu.judiciary.model.domain.AccountDO;
import com.yuantu.judiciary.model.domain.OrganizationDO;
import com.yuantu.judiciary.model.domain.SysOperLogApiDO;
import com.yuantu.judiciary.model.domain.VipOrderDO;
import com.yuantu.judiciary.model.dto.StatisticsCountDTO;
import com.yuantu.judiciary.model.enums.AuthState;
import com.yuantu.judiciary.model.enums.OrganizationType;
import com.yuantu.judiciary.model.enums.SearchType;
import com.yuantu.judiciary.model.form.GetStatisticsCountForm;
import com.yuantu.judiciary.model.form.GetStatisticsForm;
import com.yuantu.judiciary.model.vo.*;
import com.yuantu.judiciary.service.IConsoleService;
import com.yuantu.judiciary.utils.AssertUtil;
import com.yuantu.judiciary.utils.DoubleUtil;
import com.yuantu.judiciary.utils.TimeUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 控制台
 *
 * @author syw
 * @since 2023-09-01
 **/
@Service
@RequiredArgsConstructor
public class ConsoleServiceImpl implements IConsoleService {

    private final IAccountDao accountDao;
    private final IVipOrderDao vipOrderDao;
    private final IOrganizationDao organizationDao;
    private final ISysOperLogApiDao sysOperLogApiDao;

    @Override
    public StatisticsNewUserVO getNewUserStatistics(GetStatisticsForm form) {
        Timestamp startTime = ObjectUtils.isEmpty(form.getStartTime()) ? null : TimeUtil.getDayBeginTime(form.getStartTime());
        Timestamp endTime = ObjectUtils.isEmpty(form.getEndTime()) ? null : TimeUtil.getDayEndTime(form.getEndTime());

        StatisticsNewUserVO res = new StatisticsNewUserVO();
        List<IAccountDao.AccountInfo> verifyAccount = accountDao.getVerifyAccount(startTime, endTime);
        if (!CollectionUtils.isEmpty(verifyAccount)) {
            List<IAccountDao.AccountInfo> enterList = verifyAccount.stream().filter(data -> OrganizationType.ENTER.equals(data.getOrganizationType())).collect(Collectors.toList());
            List<IAccountDao.AccountInfo> singleList = verifyAccount.stream().filter(data -> OrganizationType.SINGLE.equals(data.getOrganizationType())).collect(Collectors.toList());

            Map<Date, Long> enterData = enterList.stream().collect(Collectors.groupingBy(item ->
                    TimeUtil.transferTimeDate(item.getCreateTime(), Regulation.YEAR_MONTH_DAY), Collectors.counting()));
            Map<Date, Long> singleData = singleList.stream().collect(Collectors.groupingBy(item ->
                    TimeUtil.transferTimeDate(item.getCreateTime(), Regulation.YEAR_MONTH_DAY), Collectors.counting()));

            Date minDate;
            Date maxDate;
            if (CollectionUtils.isEmpty(enterData)) {
                minDate = new Date(singleData.keySet().stream().mapToLong(Date::getTime).min().getAsLong());
                maxDate = new Date(singleData.keySet().stream().mapToLong(Date::getTime).max().getAsLong());
            } else if (CollectionUtils.isEmpty(singleData)) {
                minDate = new Date(enterData.keySet().stream().mapToLong(Date::getTime).min().getAsLong());
                maxDate = new Date(enterData.keySet().stream().mapToLong(Date::getTime).max().getAsLong());
            } else {
                minDate = new Date(Math.min(enterData.keySet().stream().mapToLong(Date::getTime).min().getAsLong(),
                        singleData.keySet().stream().mapToLong(Date::getTime).min().getAsLong()));
                maxDate = new Date(Math.max(enterData.keySet().stream().mapToLong(Date::getTime).max().getAsLong(),
                        singleData.keySet().stream().mapToLong(Date::getTime).max().getAsLong()));
            }
            List<String> xAxis = TimeUtil.getMonthBetween(minDate, maxDate, Regulation.YEAR_MONTH_DAY, Calendar.DATE);
            res.setXAxis(xAxis);
            List<Integer> singleAxis = xAxis.stream().map(data -> singleData.getOrDefault(TimeUtil.transferTimeDate(data, Regulation.YEAR_MONTH_DAY), 0L).intValue()).collect(Collectors.toList());
            res.setSingleAxis(singleAxis);
            List<Integer> enterAxis = xAxis.stream().map(data -> enterData.getOrDefault(TimeUtil.transferTimeDate(data, Regulation.YEAR_MONTH_DAY), 0L).intValue()).collect(Collectors.toList());
            res.setEnterAxis(enterAxis);
        }

        return res;
    }

    @Override
    public StatisticsChargeVO getChargeStatistics(GetStatisticsForm form) {
        Timestamp startTime = ObjectUtils.isEmpty(form.getStartTime()) ? null : TimeUtil.getDayBeginTime(form.getStartTime());
        Timestamp endTime = ObjectUtils.isEmpty(form.getEndTime()) ? null : TimeUtil.getDayEndTime(form.getEndTime());

        StatisticsChargeVO res = new StatisticsChargeVO();
        List<VipOrderDO> orders = vipOrderDao.findOrderByPayStateAndDeletedFalse(startTime, endTime);
        if (!CollectionUtils.isEmpty(orders)) {
            List<VipOrderDO> singleList = orders.stream().filter(data -> OrganizationType.SINGLE.equals(data.getOrgType())).collect(Collectors.toList());
            List<VipOrderDO> enterList = orders.stream().filter(data -> OrganizationType.ENTER.equals(data.getOrgType())).collect(Collectors.toList());

            Map<Date, List<Double>> enterData = enterList.stream().collect(Collectors.groupingBy(item ->
                    TimeUtil.transferTimeDate(item.getPayTime(), Regulation.YEAR_MONTH_DAY), Collectors.mapping(VipOrderDO::getTotalPrice, Collectors.toList())));
            Map<Date, List<Double>> singleData = singleList.stream().collect(Collectors.groupingBy(item ->
                    TimeUtil.transferTimeDate(item.getPayTime(), Regulation.YEAR_MONTH_DAY), Collectors.mapping(VipOrderDO::getTotalPrice, Collectors.toList())));

            Date minDate;
            Date maxDate;
            if (CollectionUtils.isEmpty(enterData)) {
                minDate = new Date(singleData.keySet().stream().mapToLong(Date::getTime).min().getAsLong());
                maxDate = new Date(singleData.keySet().stream().mapToLong(Date::getTime).max().getAsLong());
            } else if (CollectionUtils.isEmpty(singleData)) {
                minDate = new Date(enterData.keySet().stream().mapToLong(Date::getTime).min().getAsLong());
                maxDate = new Date(enterData.keySet().stream().mapToLong(Date::getTime).max().getAsLong());
            } else {
                minDate = new Date(Math.min(enterData.keySet().stream().mapToLong(Date::getTime).min().getAsLong(),
                        singleData.keySet().stream().mapToLong(Date::getTime).min().getAsLong()));
                maxDate = new Date(Math.max(enterData.keySet().stream().mapToLong(Date::getTime).max().getAsLong(),
                        singleData.keySet().stream().mapToLong(Date::getTime).max().getAsLong()));
            }
            List<String> xAxis = TimeUtil.getMonthBetween(minDate, maxDate, Regulation.YEAR_MONTH_DAY, Calendar.DATE);
            res.setXAxis(xAxis);
            List<Double> singleAxis = xAxis.stream().map(data -> sumDouble(singleData.getOrDefault(TimeUtil.transferTimeDate(data, Regulation.YEAR_MONTH_DAY), new ArrayList<>()))).collect(Collectors.toList());
            res.setSingleAxis(singleAxis);
            List<Double> enterAxis = xAxis.stream().map(data -> sumDouble(enterData.getOrDefault(TimeUtil.transferTimeDate(data, Regulation.YEAR_MONTH_DAY), new ArrayList<>()))).collect(Collectors.toList());
            res.setEnterAxis(enterAxis);
        }

        return res;
    }

    private Double sumDouble(List<Double> list) {
        AtomicReference<Double> res = new AtomicReference<>(0.0);
        if (!CollectionUtils.isEmpty(list)) {
            list.forEach(data -> res.set(DoubleUtil.add(res.get(), data)));
        }
        return res.get();
    }

    @Override
    public ProportionUserVO getUserProportion() {
        List<IAccountDao.AccountInfo> verifyAccount = accountDao.getVerifyAccount(null, null);
        Integer enterData = (int) verifyAccount.stream().filter(data -> OrganizationType.ENTER.equals(data.getOrganizationType())).count();
        Integer singleData = (int) verifyAccount.stream().filter(data -> OrganizationType.SINGLE.equals(data.getOrganizationType())).count();
        return new ProportionUserVO(verifyAccount.size(), enterData, singleData);
    }

    @Override
    public ProportionChargeVO getChargeProportion() {
        List<VipOrderDO> orders = vipOrderDao.findOrderByPayStateAndDeletedFalse(null, null);
        List<Double> totalData = orders.stream().map(VipOrderDO::getTotalPrice).collect(Collectors.toList());
        List<Double> enterData = orders.stream().filter(data -> OrganizationType.ENTER.equals(data.getOrgType())).map(VipOrderDO::getTotalPrice).collect(Collectors.toList());
        List<Double> singleData = orders.stream().filter(data -> OrganizationType.SINGLE.equals(data.getOrgType())).map(VipOrderDO::getTotalPrice).collect(Collectors.toList());
        return new ProportionChargeVO(sumDouble(totalData), sumDouble(enterData), sumDouble(singleData));
    }

    @Override
    public List<ProportionVipVO> getVipProportion() {
        List<VipOrderDO> orders = vipOrderDao.findOrderByPayStateAndDeletedFalse(null, null);
        if (ObjectUtils.isEmpty(orders)) {
            return Collections.emptyList();
        }
        return orders.stream().collect(Collectors.groupingBy(VipOrderDO::getVipName, Collectors.counting())).entrySet()
                .stream().map(data -> new ProportionVipVO(data.getKey(), data.getValue().intValue())
                ).collect(Collectors.toList());
    }

    @Override
    public List<OrgSimpleVO> getOrgList() {
        List<OrganizationDO> organizations = organizationDao.findOrgByAuthStateAndDeletedFalse(AuthState.PASS);
        if (CollectionUtils.isEmpty(organizations)) {
            return Collections.emptyList();
        }
        return organizations.stream().map(data ->
                new OrgSimpleVO(data.getId(), OrganizationType.ENTER.equals(data.getType()) ? data.getEnterName() : data.getUsername(), data.getType())
        ).sorted(Comparator.comparing(OrgSimpleVO::getId).reversed()).collect(Collectors.toList());
    }

    @Override
    public StatisticsCountVO getCountStatistics(GetStatisticsCountForm form) {
        Timestamp startTime = ObjectUtils.isEmpty(form.getStartTime()) ? null : TimeUtil.getDayBeginTime(form.getStartTime());
        Timestamp endTime = ObjectUtils.isEmpty(form.getEndTime()) ? null : TimeUtil.getDayEndTime(form.getEndTime());

        StatisticsCountVO res = new StatisticsCountVO();
        if (ObjectUtils.isEmpty(form.getId())) {
            List<SysOperLogApiDO> logs = sysOperLogApiDao.findByCreateTimeAndDeletedFalse(startTime, endTime);
            if (!CollectionUtils.isEmpty(logs)) {
                buildStatistics(res, logs);
            }
        } else {
            OrganizationDO organization = organizationDao.findByIdAndDeletedFalse(form.getId());
            AssertUtil.assertTrue(!ObjectUtils.isEmpty(organization), OrganizationExceptionEnum.ORGANIZATION_NOT_EXIST);
            List<AccountDO> accountList = accountDao.findAccountByOrganizationIdAndDeletedFalse(form.getId());
            if (!CollectionUtils.isEmpty(accountList)) {
                List<Long> accountIds = accountList.stream().map(AccountDO::getId).collect(Collectors.toList());
                List<SysOperLogApiDO> logs = sysOperLogApiDao.findByCreatorIdInAndCreateTimeAndDeletedFalse(accountIds, startTime, endTime);
                buildStatistics(res, logs);
            }
        }

        return res;
    }

    private static void buildStatistics(StatisticsCountVO res, List<SysOperLogApiDO> logs) {
        List<StatisticsCountDTO> enterList = new ArrayList<>();
        List<StatisticsCountDTO> personList = new ArrayList<>();

        Map<String, Long> enterStatistics = logs.stream().filter(data -> SearchType.ENTERPRISE.equals(data.getType())).collect(Collectors.groupingBy(SysOperLogApiDO::getDescription, Collectors.counting()));
        Map<String, Long> personStatistics = logs.stream().filter(data -> SearchType.PERSON.equals(data.getType())).collect(Collectors.groupingBy(SysOperLogApiDO::getDescription, Collectors.counting()));

        enterStatistics.forEach((key, value) -> enterList.add(new StatisticsCountDTO(key, value.intValue())));
        personStatistics.forEach((key, value) -> personList.add(new StatisticsCountDTO(key, value.intValue())));

        enterList.sort(Comparator.comparing(StatisticsCountDTO::getYAxis).reversed());
        personList.sort(Comparator.comparing(StatisticsCountDTO::getYAxis).reversed());

        res.setEnterXAxis(enterList.stream().map(StatisticsCountDTO::getXAxis).collect(Collectors.toList()));
        res.setEnterYAxis(enterList.stream().map(StatisticsCountDTO::getYAxis).collect(Collectors.toList()));
        res.setSingleXAxis(personList.stream().map(StatisticsCountDTO::getXAxis).collect(Collectors.toList()));
        res.setSingleYAxis(personList.stream().map(StatisticsCountDTO::getYAxis).collect(Collectors.toList()));
    }

}
